package com.mikelouis.test.scala.impatient.chapter10_traits

import java.io.PrintWriter

/**
 * Test the order of mixed traits
 * Created by yuazhu on 4/16/2015.
 */
object TestTraitOrder extends App {

  trait Logged {
    def log(msg: String)
  }

  trait ConsoleLogger extends Logged {
    override def log(msg: String): Unit = println(msg)
  }

  trait TimestampLogger extends Logged {
    // must add abstract to indicate that the method is abstract because super.log is abstract now
    abstract override def log(msg: String): Unit = super.log(new java.util.Date() + " " + msg)
  }

  // another approach is to mix ConsoleLogger to indicate that super.log is implemented at this case
  trait ShortLogger extends Logged with ConsoleLogger {
    /** another possibility is to set maxLength as abstract to let the implementation class to forcibly set to a val */
    // val maxLength: Int
    val maxLength = 15

    /** lazy here indicates that the Array will be created only the first time used */
    // if no lazy, the arrayListInShortLogger will always have length 0
    // otherwise, it will have the size of maxLength
    lazy val arrayListInShortLogger = new Array[Int](maxLength)

    override def log(msg: String): Unit = super.log(if (msg.length <= maxLength) msg else msg.substring(0, maxLength - 3) + "...")

    def getArrayListInShortLoggerSize: Int = arrayListInShortLogger.size
  }

  trait FileLogger extends Logged {
    val out = new PrintWriter("app.log")
    out.println("# " + new java.util.Date().toString)

    def log(msg: String): Unit = {
      out.println(msg)
      out.flush()
    }
  }

  /**
   * trait created for instantiation order of val
   */
  trait FileLogger2 extends Logged {
    val filename: String
    val out = new PrintWriter("filename")

    def log(msg: String): Unit = {
      out.println(msg)
      out.flush()
    }
  }

  class Account(val initialBalance: Double) {
    protected var balance = initialBalance
    println(toString + " New account!")

    def deposit(amount: Double) = {
      balance += amount
      println(toString + " Deposit")
      balance
    }

    def withdraw(amount: Double) = {
      balance -= amount
      println(toString + " Withdrawal")
      balance
    }

    // added toString to simplify printouts
    override def toString = "Balance: " + balance
  }

  abstract class SavingsAccount(override val initialBalance: Double) extends Account(initialBalance) with Logged {
    override def withdraw(amount: Double) = {
      if (amount > balance) {
        log("Insufficient funds")
        balance
      }
      else {
        log("withdraw %s$".format(amount))
        super.withdraw(amount)
      }
    }
  }

  // main
  val acct1 = new SavingsAccount(100.0) with ConsoleLogger with TimestampLogger with ShortLogger
  acct1.withdraw(10)
  println("***************************************************************")
  val acct2 = new SavingsAccount(100.0) with ConsoleLogger with ShortLogger with TimestampLogger
  acct2.withdraw(20)
  println("***************************************************************")
  val acct3 = new SavingsAccount(100.0) with ShortLogger with TimestampLogger with ConsoleLogger
  acct3.withdraw(30)
  println("***************************************************************")
  // compile error
  //  Error:(72, 19) overriding method log in trait TimestampLogger of type (msg: String)Unit;
  //  method log in trait ShortLogger of type (msg: String)Unit needs `abstract override' modifiers
  //  val acct4 = new SavingsAccount(100.0) with TimestampLogger with ShortLogger with ConsoleLogger
  //  ^
  //  val acct4 = new SavingsAccount(100.0) with TimestampLogger with ShortLogger with ConsoleLogger
  //  acct4.withdraw(40)

  /** try overriding the maxLength in trait */
  println("***************************************************************")
  val acct5 = new SavingsAccount(100.0) with ConsoleLogger with ShortLogger with TimestampLogger {
    override val maxLength: Int = 18
  }
  acct5.withdraw(50)


  /** print size of arrayListInShortLogger */
  println("***************************************************************")
  val acct6 = new SavingsAccount(100.0) with ConsoleLogger with ShortLogger with TimestampLogger {
    override val maxLength: Int = 18
  }
  acct6.withdraw(60)
  // can get 0, 15, or 18
  // if prop in trait set type as val, will only be constructed first time it is used
  println(acct6 getArrayListInShortLoggerSize)

  // another way to control the instantiation order of value
  val acct7 = new {
    val filename = "myapp.log"
  } with SavingsAccount(100.0) with FileLogger


  /** constructor sequence for traits */
  // considering the following val
  val acct8 = new SavingsAccount(100.0) with FileLogger with ShortLogger
  // constructor order:
  // 1. Account (parent parent class),
  // 2. SavingsAccount (parent class),
  // 3. Logged (parent trait of first trait: FileLogger),
  // 4. FileLogger (first trait)
  // 5. ShortLogger (second trait)
  // 6. Anonymous class[new SavingAccount] (class)
}
